#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <fcntl.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include <histedit.h>

#ifndef BSIZE
#define BSIZE 1024
#endif

static const char *prompt = "";

static const char *normalize(const char *);
static const char *getprompt(EditLine *);

int
main(int argc, char *argv[])
{
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	char b[BSIZE];
	HistEvent hev;
	History *hist;
	EditLine *el;
	FILE *ofp;
	const char *cmd = NULL;
	const char *ofile;
	const char *editor;
	const char *line;
	char *p;
	ssize_t r;
	unsigned long ul;
	int cflag = 0;
	int fflag = 0;
	int hsize = -1;
	int ofd = -1;
	int c;

#ifdef USE_PLEDGE
	pledge("stdio rpath wpath cpath dpath tty", NULL);
#endif
	if ((editor = getenv("VISUAL")) == NULL)
		editor = getenv("EDITOR");
	setprogname(argv[0]);
	while ((c = getopt(argc, argv, "C:E:c:d:efh:p:v")) != -1)
		switch (c) {
		case 'C':
			cmd = NULL;
			cflag = 1;
			fflag = 0;
			break;
		case 'E':
			editor = optarg;
			break;
		case 'c':
			cmd = optarg;
			cflag = 0;
			fflag = 0;
			break;
		case 'd':
			ofile = optarg;
			errno = 0;
			ofd = strtoul(ofile, &p, 0);
			if (errno == 0 && (p == ofile || *p != '\0' ||
			    ofd == STDIN_FILENO ||
			    ofd == STDOUT_FILENO ||
			    ofd == STDERR_FILENO ||
			    fcntl(ofd, F_GETFD) == -1))
				errno = EINVAL;
			if (errno != 0)
				err(EX_USAGE,
				    "Invalid file-descriptor '%s'",
				    ofile);
			break;
		case 'e':
			editor = "emacs";
			break;
		case 'f':
			cmd = NULL;
			cflag = 0;
			fflag = 1;
			break;
		case 'h':
			errno = 0;
			ul = strtoul(optarg, &p, 0);
			if (errno == 0 && ul > INT_MAX)
				errno = ERANGE;
			if (errno == 0 && (p == optarg || *p != '\0'))
			if (errno != 0)
				err(EX_USAGE, "Invalid number '%s'",
				    optarg);
			hsize = ul;
			break;
		case 'o':
			break;
		case 'p':
			prompt = optarg;
			break;
		case 'v':
			editor = "vi";
			break;
		default:
			goto usage;
		}
	argc -= optind;
	argv += optind;
	if (ofd < 0) {
		if (cmd != NULL) {
			ofile = cmd;
			if ((ofp = popen(cmd, "w")) == NULL)
				err(EX_IOERR, "Could not popen '%s'",
				    cmd);
			ofd = fileno(ofp);
		} else if ((ofile = *argv) == NULL)
			goto usage;
		else if ((ofd = open(ofile, O_WRONLY | O_APPEND |
		    (cflag ? O_CREAT : 0))) < 0 && (!fflag ||
		    (mkfifo(ofile, 0600) == 0 &&
		    open(ofile, O_WRONLY | O_APPEND) < 0)))
			err(EX_CANTCREAT, "could not open '%s'", ofile);
		else
			argv++;
	}
	for (; *argv != NULL; argv++) {
		if ((c = open(*argv, O_RDONLY)) < 0)
			err(EX_NOINPUT, "could not open '%s'", *argv);
		while ((r = read(c, b, sizeof(b))) > 0)
			if (write(ofd, b, r) < 0)
				err(EX_IOERR, "error writing to %s",
				    *argv);
		if (r < 0)
			err(EX_IOERR, "error reading from %s", *argv);
		close(c);
	}
	if ((hist = history_init()) == NULL)
		errx(EX_SOFTWARE, "history_init");
	el = el_init(getprogname(), stdin, stdout, stderr);
	if (el == NULL)
		errx(EX_SOFTWARE, "el_init");
	if (prompt != NULL && el_set(el, EL_PROMPT, &getprompt) == -1)
		errx(EX_SOFTWARE, "could not set prompt function");
	editor = normalize(editor);
	if (editor != NULL && el_set(el, EL_EDITOR, editor) == -1)
		warnx("could not set the editor type to '%s'", editor);
	if (el_set(el, EL_SIGNAL, 1) == -1)
		errx(EX_SOFTWARE, "could not set signal handlers");
	if (el_set(el, EL_HIST, history, hist) == -1)
		errx(EX_SOFTWARE, "could not set history function");
	if (hsize >= 0 && history(hist, &hev, H_SETSIZE, hsize) == -1)
		errx(EX_SOFTWARE, "could not set history size");
	(void)el_source(el, NULL); /* source editrc */
#ifdef USE_PLEDGE
	pledge("stdio tty", NULL);
#endif
#ifdef USE_CAPSICUM
	if (cap_enter() == -1 && errno != ENOSYS)
		err(EX_OSERR, "could not enter capibility mode");
	cap_rights_init(&rights, CAP_READ, CAP_IOCTL);
	if (cap_rights_limit(STDIN_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "could not limit the standard input");
	cap_rights_init(&rights, CAP_WRITE, CAP_IOCTL);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "could not limit the standard output");
	cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "could not limit the standard error");
	if (cap_rights_limit(ofd, &rights) == -1 && errno != ENOSYS)
		err(EX_OSERR, "could not limit '%s'", ofile);
#endif
	while ((line = el_gets(el, &c)) != NULL && c > 0) {
		(void)history(hist, &hev, H_ENTER, line);
		if (write(ofd, line, c) < 0)
			err(EX_IOERR, "error writing to '%s'", ofile);
	}
	history_end(hist);
	el_end(el);
	close(ofd);
	return (EX_OK);
usage:
	(void)fprintf(stderr,
	    "usage: el [-Cefv] [-E editor-type] [-h hist-size] "
	    "[-p prompt] output\n"
	    "          [file ...]\n"
	    "       el [-ev] [-E editor-type] [-h hist-size] "
	    "[-p prompt] -d output-fd\n"
	    "          [file ...]\n"
	    "       el [-ev] [-E editor-type] [-h hist-size] "
	    "[-p prompt] -c command\n"
	    "          [file ...]\n");
	return (EX_USAGE);
}

const char *
normalize(const char *ed)
{
	const char *elikes[] = {
		"emacs",
		"jed",
		"jove",
		"vanubi",
		"zile"
	};
	const char *vlikes[] = {
		"vi",
		"kak"
	};
	const char *p;
	size_t i;

	if (ed == NULL)
		return (NULL);
	/* basename */
	while ((p = strchr(ed, '/')) != NULL)
		if (*(ed = p + 1) == '\0')
			return (NULL);
	for (i = 0; i < sizeof(elikes) / sizeof(*elikes); i++)
		if (strcasestr(ed, elikes[i]) != NULL)
			return ("emacs");
	for (i = 0; i < sizeof(vlikes) / sizeof(*vlikes); i++)
		if (strcasestr(ed, vlikes[i]) != NULL)
			return ("vi");
	return (*ed == '\0' ? NULL : ed);
}

const char *
getprompt(EditLine *el)
{

	return (prompt == NULL ? "" : prompt);
}
